package meta

import (
	"database/sql"
	"fmt"
	"game/game-db/orm"
	"game/game-db/orm/column"
	"game/game-db/orm/metag"
	"reflect"
	"strconv"
	"strings"
)

type EntityBaseMeta struct {
	columnsMap map[string]column.IColumnMeta //数据库列名映射
	fieldsMap  map[string]column.IColumnMeta //结构体字段名映射
	columns    []column.IColumnMeta          //列元数据
}

func (t *EntityBaseMeta) Columns() []column.IColumnMeta {
	return t.columns
}

func (t *EntityBaseMeta) ColumnMeta(columnName string) column.IColumnMeta {
	return t.columnsMap[columnName]
}

func (t *EntityBaseMeta) FieldNameByColumnName(columnName string) string {
	columnMeta, exist := t.columnsMap[columnName]
	if exist {
		return columnMeta.FieldName()
	} else {
		return ""
	}
}
func (t *EntityBaseMeta) ColumnNameByFieldName(fieldName string) string {
	columnMeta, exist := t.fieldsMap[fieldName]
	if exist {
		return columnMeta.ColumnName()
	} else {
		return ""
	}
}

func (t *EntityBaseMeta) FillValue(entity reflect.Value, columnTypes []*sql.ColumnType, dest []interface{}) {
	for i, v := range dest {
		columnType := columnTypes[i]
		fieldName := t.FieldNameByColumnName(columnType.Name())
		if fieldName == "" {
			continue
		}
		rValue := reflect.ValueOf(v).Elem()
		if rValue.IsZero() {
			continue
		}
		rValue = rValue.Elem()
		objField := entity.FieldByName(fieldName)
		if !objField.IsValid() {
			continue
		}
		columnMeta := t.fieldsMap[fieldName]
		if columnMeta == nil {
			continue
		}
		columnMeta.FillValue(objField, rValue)
	}
}

type EntityMeta struct {
	EntityBaseMeta

	tableName    string                       //表名
	tableCharset metag.Charset                //字符集
	tableComment string                       //表备注
	primaryKey   *column.PrimaryKeyColumnMeta //主键
	indexes      []*metag.IndexMetag          //索引
	entityStruct orm.IDbEntity                //实体模版
}

// 创建实体实例
func (t *EntityMeta) NewEntityPtr() reflect.Value {
	rType := reflect.TypeOf(t.entityStruct)
	objPtrValue := reflect.New(rType.Elem())
	return objPtrValue
}

func (t *EntityMeta) TableName() string {
	return t.tableName
}

func (t *EntityMeta) Indexes() []*metag.IndexMetag {
	return t.indexes
}

func (t *EntityMeta) PrimaryKey() *column.PrimaryKeyColumnMeta {
	return t.primaryKey
}

func (t *EntityMeta) PrimaryKeyName() string {
	return t.primaryKey.ColumnName()
}

func (t *EntityMeta) PrimaryKeyStrategy() metag.KeyStrategy {
	return t.primaryKey.KeyStrategy()
}

func (t *EntityMeta) GetPrimaryKeyValue(entity reflect.Value) interface{} {
	objField := entity.FieldByName(t.primaryKey.FieldName())
	return objField.Interface()
}

func (t *EntityMeta) TableCharset() metag.Charset {
	if t.tableCharset == "" {
		return metag.UTF8MB4
	} else {
		return t.tableCharset
	}
}

func (t *EntityMeta) TableComment() string {
	return t.tableComment
}

func (t *EntityMeta) TakeInsertValue(entity reflect.Value) []interface{} {
	slice := make([]interface{}, 0, len(t.columns))
	for _, column := range t.columns {
		objField := entity.FieldByName(column.FieldName())
		slice = append(slice, column.TakeValue(objField))
	}
	return slice
}

func (t *EntityMeta) TakeUpdateValue(entity reflect.Value) []interface{} {
	slice := make([]interface{}, 0)
	for _, column := range t.columns {
		if column.ColumnName() == t.PrimaryKeyName() {
			continue
		} else if column.ReadOnly() {
			continue
		} else {
			objField := entity.FieldByName(column.FieldName())
			slice = append(slice, column.TakeValue(objField))
		}
	}
	keyField := entity.FieldByName(t.primaryKey.FieldName())
	slice = append(slice, t.primaryKey.TakeValue(keyField))
	return slice
}

// 把主键设置回实体内
func (t *EntityMeta) FillKeyValue(entity reflect.Value, key int64) {
	objField := entity.FieldByName(t.primaryKey.FieldName())
	t.primaryKey.FillValue(objField, reflect.ValueOf(key))
}

func (t *EntityMeta) fillMetaValue(entityStruct orm.IDbEntity, tableMetag metag.TableMetag) {
	structFields := fieldsOf(entityStruct)

	t.entityStruct = entityStruct
	t.tableName = tableMetag.Name
	if t.tableName == "" {
		t.tableName = metag.MakeSnakeCase(reflect.TypeOf(entityStruct).Elem().Name())
	}
	t.tableCharset = tableMetag.Charset
	if t.tableCharset == "" {
		t.tableCharset = metag.UTF8MB4
	}
	t.tableComment = tableMetag.Comment
	t.columnsMap = make(map[string]column.IColumnMeta)
	t.fieldsMap = make(map[string]column.IColumnMeta)
	t.columns = make([]column.IColumnMeta, 0, len(structFields))

	primaryKeyMetag := primaryKeyOf(structFields)

	for _, structField := range structFields {
		ormTagStr := structField.Tag.Get("orm")
		if ormTagStr == "" {
			continue
		}
		columnMetag := parseTag(ormTagStr)
		var imeta column.IColumnMeta
		if structField.Name == primaryKeyMetag.Name {
			t.primaryKey = column.NewPrimaryKeyField(structField, columnMetag, primaryKeyMetag)
			imeta = t.primaryKey
		} else {
			imeta = columnMetaOf(structField, columnMetag)
		}

		t.columnsMap[imeta.ColumnName()] = imeta
		t.fieldsMap[imeta.FieldName()] = imeta
		t.columns = append(t.columns, imeta)
	}

	t.indexes = tableMetag.Indexes
}

func primaryKeyOf(structFields []reflect.StructField) metag.PrimaryKeyMetag {
	var primaryKey metag.PrimaryKeyMetag
	primaryKey.AutoBase = 1000000000
	foundKey := false
	for _, structField := range structFields {
		ormTagStr := structField.Tag.Get("orm")
		if ormTagStr == "" {
			continue
		}
		keyValues := strings.SplitN(ormTagStr, ";", -1)
		for _, v := range keyValues {
			keyValue := strings.SplitN(v, "=", -1)
			if keyValue[0] == "id" {
				if !foundKey {
					foundKey = true
					primaryKey.Name = structField.Name
				} else {
					panic("duplicate id field found in")
				}
			}
			switch keyValue[0] {
			case "id":
				if string(metag.AUTOWIRE) == keyValue[1] {
					primaryKey.KeyStrategy = metag.AUTOWIRE
				} else if string(metag.AUTO) == keyValue[1] {
					primaryKey.KeyStrategy = metag.AUTO
				} else if string(metag.IDENTITY) == keyValue[1] {
					primaryKey.KeyStrategy = metag.IDENTITY
				} else {
					panic(fmt.Sprintf("can not explain primary key strategy of %v", keyValue[1]))
				}
			case "autoBase":
				intv, _ := strconv.ParseInt(keyValue[1], 10, 64)
				primaryKey.AutoBase = intv
			default:
			}
		}
	}
	if !foundKey {
		panic("can not find primary key define")
	}
	return primaryKey
}
